/**
 * 如果对象遍历了, 只有在添加和删除的情况下才会需要执行
 * 但是如果是数组, 则是监听length的改变
 */
let activeEffect
const bucket = new WeakMap()
let effectStack = []
const ITERATE_KEY = Symbol()
function track(target, key) {
  let depsMap = bucket.get(target)
  if (!depsMap) {
    bucket.set(target, (depsMap = new Map()))
  }
  let deps = depsMap.get(key)
  if (!deps) {
    depsMap.set(key, (deps = new Set()))
  }
  if (activeEffect) {
    deps.add(activeEffect)
    activeEffect.deps.push(deps)
  }
}

function trigger(target, key, type, value) {
  const depsMap = bucket.get(target)
  if (!depsMap) return
  const effects = depsMap.get(key)
  const effectsToRun = new Set()
  effects &&
    effects.forEach((effect) => {
      if (effect !== activeEffect) {
        effectsToRun.add(effect)
      }
    })

  if (type === 'ADD' || type === 'DELETE') {
    const iterateEffects = depsMap.get(ITERATE_KEY)

    iterateEffects &&
      iterateEffects.forEach((effect) => {
        if (effect !== activeEffect) {
          effectsToRun.add(effect)
        }
      })
    if (type === 'ADD' && Array.isArray(target)) {
      const lengthEffects = depsMap.get('length')
      lengthEffects &&
        lengthEffects.forEach((effectFn) => {
          if (effectFn !== activeEffect) {
            effectsToRun.add(effectFn)
          }
        })
    }
  }

  if (key === 'length') {
    depsMap.forEach((deps, key) => {
      if (key >= value) {
        deps.forEach((effectFn) => {
          if (effectFn !== activeEffect) {
            effectsToRun.add(effectFn)
          }
        })
      }
    })
  }

  effectsToRun.forEach((effect) => {
    if (effect.options && effect.options.schedule) {
      effect.options.schedule(effect)
    } else {
      effect()
    }
  })
}
function createReactive(data, isShallow = false, isReadonly = false) {
  const obj = new Proxy(data, {
    get(target, key, receiver) {
      if (key === 'raw') {
        return target
      }
      const res = Reflect.get(target, key, receiver)
      // 处于性能考虑, 不要在Symbol和副作用函数之间建立联系
      if (!isReadonly && typeof key !== 'symbol') {
        track(target, key)
      }

      if (isShallow) {
        return res
      }

      if (typeof res === 'object' && res !== null) {
        return isReadonly ? createReactive(res, false, true) : createReactive(res)
      }

      return res
    },
    set(target, key, value, receiver) {
      if (isReadonly) {
        console.log(`属性${key}为只读`)
        return true
      }
      const oldValue = target[key]
      const type = Array.isArray(target)
        ? Number(key) < target.length
          ? 'SET'
          : 'ADD'
        : Object.prototype.hasOwnProperty.call(target, key)
        ? 'SET'
        : 'ADD'
      const res = Reflect.set(target, key, value, receiver)
      if (receiver.raw === target) {
        if (oldValue !== value && (oldValue === oldValue || value === value)) {
          trigger(target, key, type, value)
        }
      }
      return res
    },
    has(target, key) {
      return Reflect.has(target, key)
    },
    ownKeys(target) {
      // ITERATE_KEY是用于对象遍历的
      // legnth则是数组遍历时需要的键
      track(target, Array.isArray(target) ? 'length' : ITERATE_KEY)
      return Reflect.ownKeys(target)
    },
    deleteProperty(target, key) {
      if (isReadonly) {
        console.log(`${key}是只读数据不可删除`)
        return true
      }
      const hadKey = Object.prototype.toString.call(target, key)
      const res = Reflect.deleteProperty(target, key)
      if (hadKey && res) {
        trigger(target, key, 'DELETE')
      }
      return res
    }
  })
  return obj
}
function cleanup(effectFn) {
  for (let i = 0; i < effectFn.deps.length; i++) {
    const deps = effectFn.deps[i]
    deps.delete(effectFn)
  }
  effectFn.deps.length = 0
}

function effect(fn, options = {}) {
  const effectFn = () => {
    cleanup(effectFn)
    activeEffect = effectFn
    effectStack.push(effectFn)
    const res = fn()
    effectStack.pop()
    activeEffect = effectStack[effectStack.length - 1]
    return res
  }
  effectFn.deps = []
  effectFn.options = options
  if (!options.lazy) {
    effectFn()
  }
  return effectFn
}

function readonly(obj) {
  return createReactive(obj, false, true)
}

function shallowReadonly(obj) {
  return createReactive(obj, true, true)
}

// 测试
const arr = createReactive([1, 2, 3])
// 1. 修改arr的长度, 遍历重新执行
// effect(() => {
//   for (let key in arr) {
//     // 如果此处加上arr[key],那所有key都会收集这个副作用函数
//     console.log(key)
//   }
// })
// arr.length = 2 // 执行
// arr[1] = 111 // for...in中有arr[key]的时候会执行, 否则不会

// 2. 可迭代对象
// 2.1. 自定义可迭代对象
let iteratorObj = {
  val: 0,
  [Symbol.iterator]() {
    return {
      next() {
        return {
          value: iteratorObj.val++,
          done: iteratorObj.val > 5 ? true : false
        }
      }
    }
  }
}

// for (const it of iteratorObj) {
//   console.log(it)
// }

// 2.2. 内建自定义可迭代对象
let iteratorArr = [1, 2, 3]
// 可以手动启动迭代
// for...of本质上是帮我们执行了iterator方法
const iteratorFn = iteratorArr[Symbol.iterator]()
// console.log(iteratorFn.next())
// console.log(iteratorFn.next())
// console.log(iteratorFn.next())
// console.log(iteratorFn.next())
// console.log(iteratorFn.next())

// 2.3 模拟数组迭代器
let iteratorArr2 = [1, 2, 4]
iteratorArr2[Symbol.iterator] = function () {
  const target = this
  const len = target.length
  let index = 0
  return {
    next() {
      return {
        value: index < len ? target[index] : 'ok',
        done: index++ >= target.length
      }
    }
  }
}
// 可以直接迭代
// for (let item of iteratorArr2) {
//   console.log(item, 'iteratorArr2') // 最后一个会变成ok
// }
// for...of 和for...in不同, 它无需写target[key]这种形式, 就能被监听到
const iteratorArr2Proxy = createReactive(iteratorArr2)
// effect(() => {
//   for (let item of iteratorArr2Proxy) {
//     console.log(item, 'for...of...iteratorArr2Proxy') // 最后一个会变成ok
//   }
// })
// iteratorArr2Proxy[1] = 2222
// 在for...in中, SET是压根就不会导致遍历重新执行的
// 而for...of可以
// iteratorArr2[1] = '123'

// 2.4 数组的values方法就是数组内建迭代器
const iteratorArr3 = createReactive([1, 2, 3])
console.log(Array.prototype.values === Array.prototype[Symbol.iterator]) // true

// 也能迭代
for (let i of iteratorArr3.values()) {
  console.log(i, 'i')
}

// 放入副作用函数中
effect(() => {
  for (let val of iteratorArr3.values()) {
    console.log(val, 'iteratorArr3Effect')
  }
})
iteratorArr3[1] = '123'

/**
 * 小节:
 * 1. 只要是数组新增了元素, 就会改到length, 而ownKeys又将数组遍历副作用加到length的副作用中
 *    感悟: 对象遍历使用的是ITERATE_KEY, 而数组是length;
 *    两者的相同点是: 都是在遍历的时候将对应的键存入监听, 然后有新增的时候再将它们对应的副作用取出执行
 *    不同点是: length的改变会影响数组的成员的值; 而ITERATE_KEY不会
 * 2.
 */
